/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * File Name          : freertos.c
 * Description        : Code for freertos applications
 ******************************************************************************
 * @attention
 *
 * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
 * All rights reserved.</center></h2>
 *
 * This software component is licensed by ST under Ultimate Liberty license
 * SLA0044, the "License"; You may not use this file except in compliance with
 * the License. You may obtain a copy of the License at:
 *                             www.st.com/SLA0044
 *
 ******************************************************************************
 */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
// SPI读取字节数据的数量＝800byte
#define SPI8Bit (800)
//转为32位4byte,stm32flash地址写入
#define SPI32Bit ((SPI8Bit)/4)
//stm32flash每次写入4byte的数量800/2＝400*4byte=1.6Kbyte
#define flashWriteSize ((SPI8Bit)/2)
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
//固件的版本号和信息存放于SPI地址
typedef struct {
	uint32_t progrm_size; // 程序固件大小size;
	uint16_t version; // 程序版本号
	uint16_t CheckSum; // 程序checkSum
	uint8_t ucType; // 程序名称标志
	uint8_t ucSubType; // 暂存标志
	uint8_t ucRserv[2]; // IAP更新标志位
} BLT_date_t;
BLT_date_t StoryData[2], *Version_Spi;
// 新建结构体管理flash的读取操作
typedef struct {
	union data {
		uint8_t rBuff[SPI8Bit]; // 自定义暂存的buffer
		uint32_t r32data[SPI32Bit];
	} Spidatabuffer;
	uint32_t rnumber; // 定义偏移地址
	uint32_t readflash32hex[flashWriteSize]; // 32位写入stm32flash的固件数
	uint16_t usCalcCheckSum; // 读取SPI所有数据的校验和
	uint16_t spiCheckSum; // SPI拉取出来的数据校验和
	uint16_t firmwarechecksum; // 固件下载完成后的checksum校验值；
	uint32_t flash_address; // stm32固件地址暂存
	uint8_t updateflag; // 更新标志位
	BLT_date_t StoryData[2]; // 拉取SPI存储的版本号和信息
} ReadFlash;
ReadFlash ReadFlashSPI, *ReadFlash_Spi; // 消息队列存储
		/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */

/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = { .name = "defaultTask",
		.stack_size = 1024 * 4, .priority =
				(osPriority_t) osPriorityAboveNormal7, };
/* Definitions for Boot_task */
osThreadId_t Boot_taskHandle;
const osThreadAttr_t Boot_task_attributes = { .name = "Boot_task", .stack_size =
		1024 * 4, .priority = (osPriority_t) osPriorityHigh, };
/* Definitions for Application_tas */
osThreadId_t Application_tasHandle;
const osThreadAttr_t Application_tas_attributes =
		{ .name = "Application_tas", .stack_size = 64 * 4, .priority =
				(osPriority_t) osPriorityAboveNormal6, };
/* Definitions for Version_task */
osThreadId_t Version_taskHandle;
const osThreadAttr_t Version_task_attributes = { .name = "Version_task",
		.stack_size = 128 * 4, .priority = (osPriority_t) osPriorityLow, };
/* Definitions for ReadQueue */
osMessageQueueId_t ReadQueueHandle;
const osMessageQueueAttr_t ReadQueue_attributes = { .name = "ReadQueue" };
/* Definitions for VersionQueue */
osMessageQueueId_t VersionQueueHandle;
const osMessageQueueAttr_t VersionQueue_attributes = { .name = "VersionQueue" };
/* Definitions for myMutex01 */
osMutexId_t myMutex01Handle;
const osMutexAttr_t myMutex01_attributes = { .name = "myMutex01" };
/* Definitions for myBinarySem01 */
osSemaphoreId_t myBinarySem01Handle;
const osSemaphoreAttr_t myBinarySem01_attributes = { .name = "myBinarySem01" };

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* Definitions for myBinarySem01 */

/* USER CODE BEGIN 4 */
#if 1
static void JumpToApplication(void) {
	uint32_t i = 0;
	void (*SysMemBootJump)(void); /* 声明一个函数指针 */
	__IO uint32_t BootAddr = 0x8010000; /* APP 地址 */

	__set_PRIMASK(1); /* 禁止全局中断 */

	/* 关闭滴答定时器，复位到默认值 */
	SysTick->CTRL = 0;
	SysTick->LOAD = 0;
	SysTick->VAL = 0;

	/* 设置所有时钟到默认状态，使用HSI时钟 */
	HAL_RCC_DeInit();

	/* 关闭所有中断，清除所有中断挂起标志 */
	for (i = 0; i < 8; i++) {
		NVIC->ICER[i] = 0xFFFFFFFF;
		NVIC->ICPR[i] = 0xFFFFFFFF;
	}

	// __set_PRIMASK(0); /* 使能全局中断 */

	/* 跳转到系统BootLoader，首地址是MSP，地址+4是复位中断服务程序地址 */
	SysMemBootJump = (void (*)(void)) (*((uint32_t*) (BootAddr + 4)));

	/* 设置主堆栈指针 */
	__set_MSP(*(uint32_t*) BootAddr);

	/* 在RTOS工程，这条语句很重要，设置为特权级模式，使用MSP指针 */
	__set_CONTROL(0);

	/* 跳转到系统BootLoader */
	SCB->VTOR = (0x8000000 | (0x10000 & (uint32_t) 0xFFFFFE00)); //设置NVIC的向量表偏移寄存9位，[8:0]保留
	/* 跳转到系统BootLoader */
	SysMemBootJump();

	/* 跳转成功的话，不会执行到这里，用户可以在这里添加代码 */
}

// 显示固件下载进度：取下载固件的百分比，即当前正在下载的固件大小
void firmware_upgrade_display(void) {
	ReadFlash *displayprocess;
	displayprocess = &ReadFlashSPI;
	char str[7] = { 0 }, str1[7] = { 0 };
	// 置画笔蓝色
	POINT_COLOR = RED;
	LCD_ShowString(50, 400, 400, 24, 24, "Upgrading...don't power off please",
			0);
	LCD_ShowString(50, 450, 400, 24, 24, "Device will restart", 0);
	// 显示正在下载的信息
	LCD_ShowString(192 + 68, 630, 50, 24, 24, "byte", 0);
	LCD_ShowString(180 - 12, 630, 24, 24, 24, "/", 0);
	// loading process

	// 把进度值转换为字符串
	snprintf(str, 7, "%d", (int) displayprocess->rnumber);
	LCD_ShowString(180, 600, 84, 24, 24, str, 0);
	// 固件总大小显示
	snprintf(str1, 7, "%d", (int) displayprocess->StoryData[1].progrm_size);
	LCD_ShowString(180, 630, 84, 24, 24, str1, 0);
}
#endif
/* USER CODE END 4 */

/* USER CODE END FunctionPrototypes */

void
StartDefaultTask(void *argument);
void
Start_Boot_task(void *argument);
void
StartApplication_task(void *argument);
void
StartVersion_task(void *argument);

void
MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/**
 * @brief  FreeRTOS initialization
 * @param  None
 * @retval None
 */
void MX_FREERTOS_Init(void) {
	/* USER CODE BEGIN Init */
	// 初始化显示屏放在接收消息数据中起到延时作用，避开消息队列数据更新，这个任务应该在消息队列之后启动
	lcdtest();

	memset(Version_Spi, 0, sizeof(StoryData)); //初始化结构体
	// 初始化结构体及指针
	ReadFlash_Spi = &ReadFlashSPI;
	memset(&ReadFlashSPI, 0, sizeof(ReadFlash));
	/* USER CODE END Init */
	/* Create the mutex(es) */
	/* creation of myMutex01 */
	myMutex01Handle = osMutexNew(&myMutex01_attributes);

	/* USER CODE BEGIN RTOS_MUTEX */
	/* add mutexes, ... */
	/* USER CODE END RTOS_MUTEX */

	/* Create the semaphores(s) */
	/* creation of myBinarySem01 */
	myBinarySem01Handle = osSemaphoreNew(1, 1, &myBinarySem01_attributes);

	/* USER CODE BEGIN RTOS_SEMAPHORES */
	/* add semaphores, ... */
	/* USER CODE END RTOS_SEMAPHORES */

	/* USER CODE BEGIN RTOS_TIMERS */
	/* start timers, add new ones, ... */
	/* USER CODE END RTOS_TIMERS */

	/* Create the queue(s) */
	/* creation of ReadQueue */
	ReadQueueHandle = osMessageQueueNew(1, sizeof(ReadFlashSPI),
			&ReadQueue_attributes);

	/* creation of VersionQueue */
	VersionQueueHandle = osMessageQueueNew(1, sizeof(StoryData),
			&VersionQueue_attributes);

	/* USER CODE BEGIN RTOS_QUEUES */
	/* add queues, ... */
	/* USER CODE END RTOS_QUEUES */

	/* Create the thread(s) */
	/* creation of defaultTask */
	defaultTaskHandle = osThreadNew(StartDefaultTask, NULL,
			&defaultTask_attributes);

	/* creation of Boot_task */
	Boot_taskHandle = osThreadNew(Start_Boot_task, NULL, &Boot_task_attributes);

	/* creation of Application_tas */
	Application_tasHandle = osThreadNew(StartApplication_task, NULL,
			&Application_tas_attributes);

	/* creation of Version_task */
	Version_taskHandle = osThreadNew(StartVersion_task, NULL,
			&Version_task_attributes);

	/* USER CODE BEGIN RTOS_THREADS */
	/* add threads, ... */
	// 挂起跳转任务，等待flash刷完之后再恢复跳转
	vTaskSuspend(Application_tasHandle);
	// 取读固件更新信息和版本号
	W25qxx_ReadBytes((uint8_t*) ReadFlash_Spi->StoryData,
	FLASH_PROGRAM_UPDATA_FLG, sizeof(StoryData));
	/* USER CODE END RTOS_THREADS */
	// 版本信息必要的调试打印
	printf(
			" \r\n StoryData[0].progrm_size=%d\r\n StoryData[0].version= %d\r\n StoryData[0].CheckSum= 0x%x \r\nStoryData[0].ucType= 0x%x \r\n",
			(int) ReadFlash_Spi->StoryData[0].progrm_size,
			(int) ReadFlash_Spi->StoryData[0].version,
			(int) ReadFlash_Spi->StoryData[0].CheckSum,
			(int) ReadFlash_Spi->StoryData[0].ucType);
	printf(
			"\r\n ReadFlash_Spi->StoryData[0].ucRserv[0]=%d\r\n\r\n ReadFlash_Spi->StoryData[0].ucRserv[1]=%d\r\n",
			(int) ReadFlash_Spi->StoryData[0].ucRserv[0],
			(int) ReadFlash_Spi->StoryData[0].ucRserv[1]);
	printf(
			" \r\n STM32.progrm_size=%d\r\n STM32.version= %d\r\n STM32.CheckSum= 0x%x \r\nSTM32.ucType= 0x%x \r\n",
			(int) ReadFlash_Spi->StoryData[1].progrm_size,
			(int) ReadFlash_Spi->StoryData[1].version,
			(int) ReadFlash_Spi->StoryData[1].CheckSum,
			(int) ReadFlash_Spi->StoryData[1].ucType);
	printf(
			"\r\n ReadFlash_Spi->StoryData[1].ucRserv[0]=%d\r\n\r\n ReadFlash_Spi->StoryData[1].ucRserv[1]=%d\r\n",
			(int) ReadFlash_Spi->StoryData[1].ucRserv[0],
			(int) ReadFlash_Spi->StoryData[1].ucRserv[1]);
	/* USER CODE BEGIN RTOS_EVENTS */
	/* add events, ... */
	/* USER CODE END RTOS_EVENTS */

}

/* USER CODE BEGIN Header_StartDefaultTask */
/**
 * @brief  Function implementing the defaultTask thread.
 * @param  argument: Not used
 * @retval None
 */

/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument) {
	/* USER CODE BEGIN StartDefaultTask */

	// 初始化Application固件起始地址
	ReadFlash_Spi->flash_address = StartServerManageFlashAddress
			- (flashWriteSize * 4);
	osStatus_t status; // 函数执行错误状态标志位
	/* Infinite loop */
	for (;;) {
		osMutexAcquire(myMutex01Handle, osWaitForever);
#if 1   // 判断固件更新
		// 取SPIflash中的标志位，没有新固件下载直接跳到应用程序
		if ((ReadFlash_Spi->StoryData[1].ucRserv[0] == 0)
				|| (ReadFlash_Spi->StoryData[1].ucRserv[0] != 1)) {


			// 发送生版本号消息队列，由下一个任务打印
			status = osMessageQueuePut(VersionQueueHandle, &ReadFlash_Spi,
					osPriorityAboveNormal7, osWaitForever);
			// 获取固件大小
			ReadFlash_Spi->rnumber = ReadFlash_Spi->StoryData[1].progrm_size;
			// 获取check_Sum
			ReadFlash_Spi->usCalcCheckSum =
					ReadFlash_Spi->StoryData[1].CheckSum;
			// 释放跳转任务的信号量
			osSemaphoreRelease(myBinarySem01Handle);
			// 校验结束，恢复跳转任务
			vTaskResume(Application_tasHandle);

		}
#endif
		// 调试
		// printf("\r\n StartDefaultTask");
		// 进入更新从SPI_flash中读取STM32固件更新程序
		else if ((ReadFlash_Spi->rnumber)
				< (ReadFlash_Spi->StoryData[1].progrm_size)) {


			// 读取SPI中的flash固件：注意for语句中如果用uint8_t只能报0－254个字节，若要一次读取更多要用uint16_t/uint32_t;
			for (uint16_t j = 0; j < flashWriteSize; j += SPI32Bit)
			{
				W25qxx_ReadBytes(ReadFlash_Spi->Spidatabuffer.rBuff,
				FLASH_STM32_CODE_START + ReadFlash_Spi->rnumber,
				SPI8Bit);		//取高位到低位顺序

				// 这里判断偏移地址和校验包的长度是否一致,若不一致,则校验SPI读出来的数据是否校验成功
				if ((ReadFlash_Spi->rnumber
						> ((ReadFlash_Spi->StoryData[1].progrm_size) - SPI8Bit))
						&& (!ReadFlash_Spi->updateflag)) {
					/* SPIchecksum_地址的偏移标志位清零 */
					ReadFlash_Spi->updateflag = 1;
					uint16_t idexSPInuber = ( SPI8Bit
							- (ReadFlash_Spi->rnumber
									- ((ReadFlash_Spi->StoryData[1].progrm_size)
											- SPI8Bit)));
					for (uint16_t i = 0; i < idexSPInuber; i++) {
						// 求校验和
						ReadFlash_Spi->usCalcCheckSum +=
								ReadFlash_Spi->Spidatabuffer.rBuff[i];
						if (ReadFlash_Spi->rnumber + i
								== (ReadFlash_Spi->StoryData[1].progrm_size)
										- 1)
							break;

					}
				} else if (ReadFlash_Spi->updateflag == 0)
					for (uint16_t i = 0; i < SPI8Bit; i++)		//求校验和

						ReadFlash_Spi->usCalcCheckSum +=
								ReadFlash_Spi->Spidatabuffer.rBuff[i];

				// 这里用联合体将SPI读取的8位数据转32位之后存到将要更新的buffer
				for (uint16_t i = 0; i < SPI32Bit; i++)
					ReadFlash_Spi->readflash32hex[i + j] =
							ReadFlash_Spi->Spidatabuffer.r32data[i];

				// 调试
				// printf("ReadFlash_Spi->Spidatabuffer.r32data=0x%x",ReadFlash_Spi->Spidatabuffer.r32data);

				// 调试
				// printf(" 10Spidatabuffer.rBuff0=%x Spidatabuffer.rBuff1= 0x%x Spidatabuffer.rBuff2= 0x%x Spidatabuffer.rBuff3= 0x%x \r\n",ReadFlash_Spi->Spidatabuffer.rBuff[0],ReadFlash_Spi->Spidatabuffer.rBuff[1],ReadFlash_Spi->Spidatabuffer.rBuff[2],ReadFlash_Spi->Spidatabuffer.rBuff[3]);
				// 更新buffer之后STM32_flash地址+SPI8Bit
				ReadFlash_Spi->rnumber += SPI8Bit;

				ReadFlash_Spi->flash_address += SPI8Bit;
				/*更新checksum */
				ReadFlash_Spi->spiCheckSum = ReadFlash_Spi->usCalcCheckSum;

			}
			status = osMessageQueuePut(ReadQueueHandle, &ReadFlash_Spi,
					osPriorityHigh, osWaitForever);
		}
		/* 当程序包的size是ReadFlash_Spi->rnumber的整数倍则进入 */
		else {
			// 打印最后一次将要写的地址
			printf("\r\n last_flash_address= 0x%x\r\n",
					(int) ReadFlash_Spi->flash_address);
			// 停止更新挂起本身,调试可用
			vTaskSuspend(defaultTaskHandle);
		}

		if (status == osOK)
			osMutexRelease(myMutex01Handle);
		osDelay(1); //这个延时必须要的;
	}
	/* USER CODE END StartDefaultTask */
}

/* USER CODE BEGIN Header_Start_Boot_task */
/**
 * @brief Function implementing the Boot_task thread.
 * @param argument: Not used
 * @retval None
 */

/* USER CODE END Header_Start_Boot_task */
void Start_Boot_task(void *argument) {
	/* USER CODE BEGIN Start_Boot_task */
	// 重构一个结构体存放消息队列的信息；
	ReadFlash msg, *ptMsg;
	osStatus_t xResult;
	//初始化结构体
	memset(&msg, 0, sizeof(msg));
	/* Infinite loop */
	// 初始化结构体指针
	ptMsg = &msg;
	// 这里画一个圆形代表进度更新提示,圆径不能开太大，吃内存容易死机
	LCD_Draw_Circle(230, 630, 92);
	error_t error;
	for (;;) {
		//调试
		//	printf("\r\nStart_Boot_task\r\n ");
		xResult = osMessageQueueGet(ReadQueueHandle, &ptMsg, 0, osWaitForever);
#if 1
		if (xResult == osOK) {
			osMutexAcquire(myMutex01Handle, osWaitForever);

#if 0
			/* 成功接收，并通过串口将数据打印出  */
			printf("ptMsg->Spidatabuffer.rBuff[0] = %x\r\n",ptMsg->Spidatabuffer.rBuff[0]);
			printf("ptMsg->Spidatabuffer.rBuff[1] = %x\r\n", ptMsg->Spidatabuffer.rBuff[1]);
			printf("ptMsg->Spidatabuffer.rBuff[2] = %x\r\n", ptMsg->Spidatabuffer.rBuff[2]);
			printf("ptMsg->Spidatabuffer.rBuff[3] = %x\r\n", ptMsg->Spidatabuffer.rBuff[3]);
			printf("ptMsg->r_number = %d\r\n", (int)ptMsg->rnumber);
			printf("ptMsg->r_usCalcCheckSum = 0x%x\r\n", (int)ptMsg->usCalcCheckSum);
			printf("ptMsg->r_firmwarechecksum = 0x%x\r\n", (int)ptMsg->firmwarechecksum);
			printf("ptMsg->rreadflash32hex = 0x%x\r\n", (int)ptMsg->readflash32hex);
#endif

			// Program the 32-bit words to Flash memory写固件到flash
			error = MoreFlashWrite(ptMsg->flash_address, ptMsg->readflash32hex,
					sizeof(ptMsg->readflash32hex));

			// debug message:打印当前固件数的指针
			// static uint32_t p[1];
			// read_from_flash(ptMsg->flash_address,p,1);
			// printf("\nread_flash:0X%x\n",(int)p[0]);

		} else {
			// debug message:
			printf("osMessageQueueGet failed\r\n");
		}
#endif
		// 显示当前升级进度
		firmware_upgrade_display();
		// printf("\r\nptMsg->number:%d\n",(int)ptMsg->rnumber);
		if (ptMsg->rnumber >= (ReadFlash_Spi->StoryData[1].progrm_size)
				&& (error == NO_ERROR)) //标记已下载完成固件，跳转到APP应用程序执行
				{
			// 判断更新标志位和固件checksum是否成立，只有check sum成立才跳转
			ptMsg->firmwarechecksum = ReadSTM32checksum(
			StartServerManageFlashAddress,
					ReadFlash_Spi->StoryData[1].progrm_size);
			// 判断从SPI_flash中读取的校验和&flash下载后的校验和是否一致，成功即跳转
			if (ptMsg->spiCheckSum == ptMsg->firmwarechecksum)

			{
				// 释放跳转任务的信号量//校验结束，恢复跳转任务
				vTaskResume(Application_tasHandle);
				osSemaphoreRelease(myBinarySem01Handle);

			} else // 校验错误
			{
				printf("\r\nCheckSum_Failed\r\n");
				// 调试
				printf("\r\nptMsg->spiCheckSum =0x%x\r\n",
						(int) ptMsg->spiCheckSum);
				printf("\r\nptMsg->firmware_checksum =%x\r\n",
						(int) ptMsg->firmwarechecksum);
			}

		}

		osMutexRelease(myMutex01Handle);

#if 0
		if (ptMsg->rnumber % 1000 == 0)
			printf("\r\n %d\n", (int) ptMsg->rnumber);
#endif

	}
	/* USER CODE END Start_Boot_task */
}

/* USER CODE BEGIN Header_StartApplication_task */
/**
 * @brief Function implementing the Application_tas thread.
 * @param argument: Not used
 * @retval None
 */

/* USER CODE END Header_StartApplication_task */
void StartApplication_task(void *argument) {
	/* USER CODE BEGIN StartApplication_task */

	/* Infinite loop */
	for (;;) {


		//调试
	//	printf("\r\n Jump_toApplication_task \r\n");
		osMutexRelease(myMutex01Handle);
		JumpToApplication();
	}
	/* USER CODE END StartApplication_task */
}

/* USER CODE BEGIN Header_StartVersion_task */
/**
 * @brief Function implementing the Version_task thread.
 * @param argument: Not used
 * @retval None
 */
/* USER CODE END Header_StartVersion_task */
void StartVersion_task(void *argument) {
	/* USER CODE BEGIN StartVersion_task */
	// 重构结构体存放消息队列的信息；
	ReadFlash msg, *version;
	osStatus_t xResult;
	// 初始化生版本号的指针
	version = &msg;
	/* Infinite loop */
	for (;;) {

		xResult = osMessageQueueGet(VersionQueueHandle, &version, 0,
		osWaitForever);

		if (xResult == osOK) {
#if 0
			osMutexAcquire(myMutex01Handle, osWaitForever);
			printf(
					" \r\n VSprogrm_size=%d\r\n VSversion= %d\r\n VSCheckSum= 0x%x \r\nVSucType= 0x%x \r\n",
					(int) version->StoryData[0].progrm_size,
					(int) version->StoryData[0].version,
					(int) version->StoryData[0].CheckSum,
					(int) version->StoryData[0].ucType);
			printf(
					" \r\n STM32.progrm_size=%d\r\n STM32.version= %d\r\n STM32.CheckSum= 0x%x \r\nSTM32.ucType= 0x%x \r\n",
					(int) version->StoryData[1].progrm_size,
					(int) version->StoryData[1].version,
					(int) version->StoryData[1].CheckSum,
					(int) version->StoryData[1].ucType);
			printf("\r\n STM32.Iap_flag=%d\r\n",
					(int) version->StoryData[1].ucRserv[0]);
#endif

		}

		osMutexRelease(myMutex01Handle);


	}
	/* USER CODE END StartVersion_task */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */

/* USER CODE END Application */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
